﻿namespace PugLib.Extensions
{
	using System;
	using System.Collections.Generic;
	using System.Globalization;
	using System.Linq.Expressions;
	using System.Threading;

	using Collections;

	public static class ActionExtensions
	{
		public static Action MakeParallel(this Action t)
		{
			return () =>
			{
				using (Waitlist w = new Waitlist())
				{
					t.GetInvocationList().ForEach<Action>(a => w.Add(a.BeginInvoke(null, null)));
				}
			};
		}

		public static Action<T1> MakeParallel<T1>(this Action<T1> t)
		{
			return x =>
			{
				using (Waitlist w = new Waitlist())
				{
					t.GetInvocationList().ForEach<Action<T1>>(a => w.Add(a.BeginInvoke(x, null, null)));
				}
			};
		}

		public static Action<T1, T2> MakeParallel<T1, T2>(this Action<T1, T2> t)
		{
			return (x, y) =>
			{
				using (Waitlist w = new Waitlist())
				{
					t.GetInvocationList().ForEach<Action<T1, T2>>(a => w.Add(a.BeginInvoke(x, y, null, null)));
				}
			};
		}

		public static Action<T1, T2, T3> MakeParallel<T1, T2, T3>(this Action<T1, T2, T3> t)
		{
			return (x, y, z) =>
			{
				using (Waitlist w = new Waitlist())
				{
					t.GetInvocationList().ForEach<Action<T1, T2, T3>>(a => w.Add(a.BeginInvoke(x, y, z, null, null)));
				}
			};
		}

		public static Action<T1, T2, T3, T4> MakeParallel<T1, T2, T3, T4>(this Action<T1, T2, T3, T4> t)
		{
			return (x, y, z, v) =>
			{
				using (Waitlist w = new Waitlist())
				{
					t.GetInvocationList().ForEach<Action<T1, T2, T3, T4>>(a => w.Add(a.BeginInvoke(x, y, z, v, null, null)));
				}
			};
		}

		/// <summary>
		/// Useful for wrapping actions in a transaction-like process when you don't control how many threads may call it at once. 
		/// </summary>
		/// <param name="action"></param>
		/// <param name="globalMutexId"></param>
		public static void ExecuteInMutexWithSubsequentWaits(this Action action, string globalMutexId)
		{
			//const string exampleMutexId = "Global\\{B22CA031-0FC6-473f-8770-4F499331796D}";
			using (Mutex importLock = new Mutex(false, globalMutexId))
			{
				try
				{
					importLock.WaitOne(Timeout.Infinite, false);

					action();
				}
				finally
				{
					importLock.ReleaseMutex();
				}
			}
		}

		/// <summary>
		/// Abstracts the single action instance mutex.
		/// </summary>
		/// <param name="action"></param>
		/// <param name="globalMutexId"></param>
		public static void ExecuteInMutex(this Action action, string globalMutexId)
		{
			//const string exampleMutexId = "Global\\{B22CA031-0FC6-473f-8770-4F499331796D}";
			using (Mutex singletonMutex = new Mutex(false, globalMutexId))
			{
				try
				{
					// Ensure this is the first and only instance.
					if (!singletonMutex.WaitOne(0, false))
					{
						return;
					}

					singletonMutex.WaitOne(Timeout.Infinite, false);

					action();
				}
				finally
				{
					singletonMutex.ReleaseMutex();
				}
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="ex"></param>
		/// <returns></returns>
		/// <exception cref="System.ArgumentException">if the expression is invalid for the type</exception>
		public static string Get<T>(Expression<Func<T, object>> ex)
		{
			string name;
			switch (ex.Body.NodeType)
			{
				case ExpressionType.MemberAccess:
					name = ex.Body.ToString();
					break;
				case ExpressionType.Convert:
					name = ((UnaryExpression) ex.Body).Operand.ToString();
					break;
				default:
					throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "Expression type {0} unknown", ex.Body.NodeType));
			}
			name = name.Substring(name.IndexOf('.') + 1); // remove the lambda name from expression (d=>d.Test to Test)
			return name;
		}
	}

	public sealed class Waitlist : IDisposable
	{
		private readonly List<IAsyncResult> waits = new List<IAsyncResult>();

		#region IDisposable Members

		public void Dispose()
		{
			waits.ForEach(ar => ar.AsyncWaitHandle.WaitOne());
			GC.SuppressFinalize(this);
		}

		#endregion

		public void Add(IAsyncResult ar)
		{
			waits.Add(ar);
		}
	}
}